package mqtt

import (
	"errors"
	"fmt"
	"github.com/eclipse/paho.mqtt.golang"
	"time"
)

const (
	QoS0 = 0 // 至多一次
	QoS1 = 1 // 至少一次
	QoS2 = 2 // 确保只有一次
)

//MQTT客户端
type MqttClient struct {
	MqttConfig                               //配置
	Conn         mqtt.Client                 //连接
	SubscribeMap map[string]MqttTopicHandler //订阅主题
	DevicesMap   map[string][]Device         //订阅设备
}

//MQTT配置
type MqttConfig struct {
	Address  string // mqtt服务器地址
	Username string // mqtt账号
	Password string // mqtt密码
	Topic    string // mqtt主题
}

//订阅消息
type MqttTopicHandler func(topic string, message []byte)

//获取MQTT客户端
func NewMqttClient(config MqttConfig) *MqttClient {
	c := new(MqttClient)
	c.MqttConfig = config
	c.SubscribeMap = map[string]MqttTopicHandler{}
	c.DevicesMap = map[string][]Device{}
	return c
}

//是否连接
func (c *MqttClient) IsConnected() bool {
	if c.Conn == nil {
		return false
	}
	return c.Conn.IsConnected()
}

//创建连接
func (c *MqttClient) Connect(forceReconn ...bool) error {
	subMap := map[string]MqttTopicHandler{}
	devicesMap := map[string][]Device{}
	if c.IsConnected() {
		if len(forceReconn) > 0 && forceReconn[0] {
			subMap = c.SubscribeMap
			devicesMap = c.DevicesMap
			c.Disconnect()
		} else {
			return nil
		}
	}

	opts := mqtt.NewClientOptions()
	opts.AddBroker(c.Address)
	opts.SetUsername(c.Username)
	opts.SetPassword(c.Password)
	opts.SetKeepAlive(60 * time.Second)
	opts.SetPingTimeout(1 * time.Second)

	client := mqtt.NewClient(opts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		return token.Error()
	}
	c.Conn = client

	for topic, topicHandler := range subMap {
		err := c.Subscribe(topic, topicHandler)
		if err != nil {
			fmt.Println("MQTT ReSubscribe Failed,", "topic:", topic, err)
		}
	}
	for _, devices := range devicesMap {
		for _, device := range devices {
			err := c.SubscribeDevice(device)
			if err != nil {
				fmt.Println("MQTT ReSubscribe Failed,", "topic:", device.MqttTopic(), "device:", device.MqttKey(), err)
			}
		}
	}

	fmt.Println("MQTT Connect Success,", "address:", c.Address, "username:", c.Username)
	return nil
}

//断开连接
func (c *MqttClient) Disconnect() {
	if !c.IsConnected() {
		return
	}

	for topic, _ := range c.SubscribeMap {
		c.Unsubscribe(topic)
	}
	c.Conn.Disconnect(250)
	c.Conn = nil
}

//发布消息
func (c *MqttClient) Publish(message []byte, topic ...string) error {
	if !c.IsConnected() {
		return errors.New("MQTT connect is nil")
	}

	Topic := c.Topic
	if len(topic) > 0 && len(topic[0]) > 0 {
		Topic = topic[0]
	}
	if len(Topic) == 0 {
		return errors.New("MQTT Publish topic is empty")
	}
	token := c.Conn.Publish(Topic, QoS0, false, message)
	if token.Wait() && token.Error() != nil {
		return token.Error()
	}
	return nil
}

//订阅主题
func (c *MqttClient) Subscribe(topic string, topicHandler MqttTopicHandler) error {
	if !c.IsConnected() {
		return errors.New("MQTT connect is nil")
	}
	if len(topic) == 0 {
		return errors.New("MQTT Subscribe topic is empty")
	}
	if topicHandler == nil {
		return errors.New("MQTT Subscribe topicHandler is nil")
	}

	if _, ok := c.SubscribeMap[topic]; ok {
		return nil
	}
	if _, ok := c.DevicesMap[topic]; ok {
		return errors.New("MQTT has subscribed by device")
	}

	token := c.Conn.Subscribe(topic, QoS0, func(client mqtt.Client, message mqtt.Message) {
		topicHandler(message.Topic(), message.Payload())
	})
	if token.Wait() && token.Error() != nil {
		return token.Error()
	}
	c.SubscribeMap[topic] = topicHandler

	fmt.Println("MQTT Subscribe,", "topic:", topic)
	return nil
}

//取消订阅
func (c *MqttClient) Unsubscribe(topic string) {
	if !c.IsConnected() {
		return
	}
	if len(topic) == 0 {
		return
	}

	c.Conn.Unsubscribe(topic)
	delete(c.SubscribeMap, topic)

	fmt.Println("MQTT Unsubscribe,", "topic:", topic)
}

//协程等待
func (c *MqttClient) Wait() {
	select {}
}
